package vue;

import controleur.EcouteurDeSouris;
import controleur.EcouteurDeSourisEdition;
import controleur.EcouteurDeSourisJeu;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.Timer;
import modele.Drapeau;
import modele.Joueur;
import modele.Plateau;
import modele.case_.Case;
import modele.coupSpecial.CoupSpecial;
import modele.pion.Pion;
import outils.ImageOutils;
import outils.gestionnairefichier.GestionnaireImage;

/**
 * Un panel qui contient la représentation du plateau ainsi que des pions des
 * différents joueurs.
 *
 * @author twane
 */
public final class AireDeJeu extends JPanel {

	private Plateau plateau;
	private BufferedImage bufferCarte;
	private BufferedImage bufferFond;
	private BufferedImage bufferInfo;
	private BufferedImage bufferPions;
	private BufferedImage bufferDeplacements;
	private BufferedImage bufferMessage;
	private BufferedImage bufferAnimation;
	private Point bufferInfoPos;
	private boolean infoOn;
	private Camera camera;
	private Point tailleCarte;
	private EcouteurDeSouris ecouteur;

	/**
	 * Crée une aire de jeu en fonction du plateau et des différents joueurs
	 *
	 * @param j le Panel contenant cette aire de jeu
	 * @param plateau le plateau de jeu à afficher
	 * @param j1 le joueur 1
	 * @param j2 le joueur 2
	 * @param fenetre la fenetre dans laquelle est affichée cette aire de jeu
	 */
	public AireDeJeu(JPanel j, Plateau plateau, Joueur j1, Joueur j2, Fenetre fenetre) {
		tailleCarte = new Point(plateau.getLargeur() * 50, plateau.getHauteur() * 50);
		Point tailleCamera = new Point(Math.min(j.getWidth(), tailleCarte.x), Math.min(j.getHeight(), tailleCarte.y));
		camera = new Camera(tailleCamera, tailleCarte);
		setLayout(new FlowLayout());
		setPreferredSize(new Dimension(tailleCamera.x, tailleCamera.y));
		setSize(new Dimension(tailleCamera.x, tailleCamera.y));
		setBackground(new java.awt.Color(255, 255, 255));
		setLocation((j.getWidth() - camera.getDimensions().x) / 2, (j.getHeight() - camera.getDimensions().y) / 2);

		setEcouteur(new EcouteurDeSourisEdition(this, plateau, j1, j2, fenetre));
		this.plateau = plateau;

		infoOn = false;

		bufferCarte = new BufferedImage(plateau.getLargeur() * Plateau.TAILLE_CASE, plateau.getHauteur() * Plateau.TAILLE_CASE, BufferedImage.TYPE_INT_ARGB);
		bufferFond = new BufferedImage(camera.getDimensions().x, camera.getDimensions().y, BufferedImage.TYPE_INT_ARGB);
		bufferPions = new BufferedImage(camera.getDimensions().x, camera.getDimensions().y, BufferedImage.TYPE_INT_ARGB);
		bufferDeplacements = new BufferedImage(camera.getDimensions().x, camera.getDimensions().y, BufferedImage.TYPE_INT_ARGB);
		bufferMessage = new BufferedImage(camera.getDimensions().x, camera.getDimensions().y, BufferedImage.TYPE_INT_ARGB);
		bufferAnimation = new BufferedImage(camera.getDimensions().x, camera.getDimensions().y, BufferedImage.TYPE_INT_ARGB);
		creerBufferCarte();
		dessinerBufferFond();
		dessinerBufferPions();
	}

	/**
	 * Ajoute l'écouteur de souris à l'aire de jeu
	 *
	 * @param ecouteur
	 */
	public void setEcouteur(EcouteurDeSouris ecouteur) {
		if (this.ecouteur != null) {
			removeMouseListener(this.ecouteur);
			removeMouseMotionListener(this.ecouteur);
		}
		this.ecouteur = ecouteur;
		addMouseListener(ecouteur);
		addMouseMotionListener(ecouteur);
	}

	/**
	 * @return l'Ecouteur De Souris qui est associé à l'aire de Jeu
	 */
	public EcouteurDeSouris getEcouteur() {
		return ecouteur;
	}

	/**
	 * @return la bufferedImage contenant la carte
	 */
	public BufferedImage getBufferCarte() {
		return bufferCarte;
	}

	/**
	 * @return	la caméra courante
	 * @see	Camera
	 */
	public Camera getCamera() {
		return camera;
	}

	/**
	 * @return	le Plateau courant
	 * @see	Plateau
	 */
	public Plateau getPlateau() {
		return plateau;
	}

	/**
	 * @return	hauteur de l'aire
	 */
	public int getHauteur() {
		return getHeight();
	}

	/**
	 * @return	largeur de l'aire
	 */
	public int getLargeur() {
		return getWidth();
	}

	/**
	 * Peint l'aire de jeu, cette méthode est appelé lors de chaque repaint
	 * éffectué sur l'aire de jeu
	 *
	 * @param g
	 */
	@Override
	synchronized public void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;

		g2.drawImage(bufferFond, null, 0, 0);
		g2.drawImage(bufferPions, null, 0, 0);
		g2.drawImage(bufferMessage, null, 0, 0);
		g2.drawImage(bufferAnimation, null, 0, 0);

		if (infoOn) {
			g2.drawImage(bufferInfo, null, (int) bufferInfoPos.getX(), (int) bufferInfoPos.getY());
		}
	}

	/**
	 * Crée la BufferedImage qui contient la carte
	 */
	private void creerBufferCarte() {
		bufferCarte = new BufferedImage(plateau.getLargeur() * Plateau.TAILLE_CASE, plateau.getHauteur() * Plateau.TAILLE_CASE, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2 = (Graphics2D) bufferCarte.createGraphics();
		plateau.dessiner(g2, null);
	}

	/**
	 * Crée la BufferedImage qui réprésente les cases sur lesquelles le joueur
	 * peut positionner ses pions lors de l'édition
	 *
	 * @param j le Joueur qui est en train de placer ses pions
	 * @param p le pions à placer sur la carte
	 */
	public void creerBufferCasesPositionnable(Joueur j, Pion p) {
		bufferDeplacements = new BufferedImage(plateau.getLargeur() * Plateau.TAILLE_CASE, plateau.getHauteur() * Plateau.TAILLE_CASE, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2 = (Graphics2D) bufferDeplacements.createGraphics();
		BufferedImage im = GestionnaireImage.getInstance().get("src/donnees/images/selectionbleue.png");
		im = ImageOutils.copyAlpha(im, 0.2f);

		for (int x = 0; x < plateau.getLargeur(); x++) {
			for (int y = 0; y < plateau.getHauteur(); y++) {
				if (plateau.isPositionnable(new Point(x, y), j) && p.peutAcceder(plateau.getCase(x, y)) && plateau.getCase(x, y).getPion() == null) {
					g2.drawImage(im, null, x * Plateau.TAILLE_CASE, y * Plateau.TAILLE_CASE);
				}
			}
		}
	}

	/**
	 * Crée la BufferedImage qui contient les pions des deux joueurs
	 */
	public void dessinerBufferPions() {
		bufferPions = new BufferedImage(camera.getDimensions().x, camera.getDimensions().y, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2 = (Graphics2D) bufferPions.createGraphics();

		ArrayList<Case> casesCamera = camera.getCases(plateau);
		for (Case c : casesCamera) {
			Pion pion = c.getPion();
			if (pion != null) {
				pion.dessiner(g2, camera);
			}
		}

		for (Drapeau d : plateau.getDrapeaux()) {
			d.dessiner(g2, camera);
		}
	}

	/*
	 * Dessine la BufferedImage qui est visible dans le champ de la caméra
	 * en fonction de la BufferedImage contenant la carte
	 * 
	 */
	private void dessinerBufferFond() {
		BufferedImage bufferTmp = new BufferedImage(plateau.getLargeur() * Plateau.TAILLE_CASE, plateau.getHauteur() * Plateau.TAILLE_CASE, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2 = (Graphics2D) bufferTmp.createGraphics();
		Graphics2D gFond = bufferFond.createGraphics();

		g2.drawImage(bufferCarte, 0, 0, null);
		g2.drawImage(bufferDeplacements, 0, 0, null);

		Rectangle vue = camera.getRectangle();

		AffineTransform tx = new AffineTransform();
		double scaleX = 1;
		double scaleY = 1;
		tx.setTransform(scaleX, 0, 0, scaleY, -vue.x, -vue.y);

		gFond.drawImage(bufferTmp, tx, null);
	}

	/**
	 * Redessine le BufferedImages contenant le Fond et les Pions
	 */
	public void actualiserBuffers() {
		dessinerBufferFond();
		dessinerBufferPions();
		repaint();
	}

	/**
	 * Dessine sur la BufferedImage bufferInfo la bulle d'information à côté du
	 * pion présent dans la caseSouris
	 *
	 * @param caseSouris la case contenant le pion qui doit avoir ses infos
	 * affichées
	 * @param pion le pions dont les informations doivent être affichés
	 */
	public void afficherInfos(Point caseSouris, Pion pion) {

		int hauteurInfoBox = 130;
		int largeurInfoBox = 130;
		int avanceInfoCase = 10;
		int espaceInterLigne = 20;
		int transparence = 150;
		float assombrissement = 0.7f;
		Rectangle vue = camera.getRectangle();
		int xBD = (caseSouris.x + 1) * Plateau.TAILLE_CASE - avanceInfoCase - vue.x;
		int yBD = (caseSouris.y + 1) * Plateau.TAILLE_CASE - avanceInfoCase - vue.y;
		infoOn = true;
		bufferInfo = new BufferedImage(largeurInfoBox, hauteurInfoBox, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2 = (Graphics2D) bufferInfo.createGraphics();
		String nom = "- " + pion + " -";
		FontMetrics fm = g2.getFontMetrics();
		int largeurNom = fm.stringWidth(nom);

		if (largeurInfoBox < largeurNom
				+ 10) {
			largeurInfoBox = largeurNom + 10;
			bufferInfo = new BufferedImage(largeurInfoBox, hauteurInfoBox, BufferedImage.TYPE_INT_ARGB);
			g2 = (Graphics2D) bufferInfo.createGraphics();
		}
		Color couleur = pion.getJoueur().getCouleur();
		Color couleur2 = new Color((int) (couleur.getRed() * assombrissement), (int) (couleur.getGreen() * assombrissement), (int) (couleur.getBlue() * assombrissement), transparence);

		g2.setColor(couleur2);

		g2.fillRect(
				0, 0, largeurInfoBox, hauteurInfoBox);
		g2.setColor(
				new Color(250, 250, 250, transparence));
		int hauteurTexte = 15;

		g2.drawString(nom,
				5, hauteurTexte);
		hauteurTexte = 45;

		for (String s : pion.getCaracteristiques()) {
			g2.drawString(s, 5, hauteurTexte);
			hauteurTexte += espaceInterLigne;
		}

		if (xBD + largeurInfoBox > getLargeur()) {
			xBD = xBD + (2 * avanceInfoCase) - Plateau.TAILLE_CASE - largeurInfoBox;
		}

		if (yBD + hauteurInfoBox > getHauteur()) {
			yBD = yBD + (2 * avanceInfoCase) - Plateau.TAILLE_CASE - hauteurInfoBox;
		}
		bufferInfoPos = new Point(xBD, yBD);


		repaint();
	}

	/**
	 * Masque la BufferedImage qui permet d'afficher les infos des pions
	 */
	public void masquerInfos() {
		if (infoOn == true) {
			infoOn = false;
			repaint();
		}
	}

	/**
	 * Retourne les coordonnées de la case dans le plateau qui se situe à
	 * 'coord'.
	 *
	 * @param coord les coordonnées en pixel de la case qui nous intéresse
	 * @return	les coordonnées dans le plateau de la case
	 */
	public Point getCaseCoordonnees(Point coord) {
		Point res;
		Rectangle vue = camera.getRectangle();

		coord.x += vue.x;
		coord.y += vue.y;

		res = new Point(coord.x / Plateau.TAILLE_CASE, coord.y / Plateau.TAILLE_CASE);
		return res;
	}

	/**
	 * Affecte le centre de la caméra aux nouvelles coordonnées passées en
	 * paramètre
	 *
	 * @param nouvellesCoord les nouvelles coordonnées de la caméra
	 */
	public void setCamera(Point nouvellesCoord) {
		camera.setCentre(nouvellesCoord);
	}

	/**
	 * Affiche un message pour indique qu'un changement de joueur a eu lieu
	 *
	 * @param joueurCourant
	 */
	public void afficherMessageChangementJoueur(Joueur joueurCourant) {
		Graphics2D g2 = (Graphics2D) bufferDeplacements.createGraphics();

		g2.setFont(new Font("impact", Font.BOLD, 20));
		g2.drawString(joueurCourant.getNom(), (int) camera.getCentre().getX(), (int) camera.getCentre().getY());
		actualiserBuffers();
	}

	/**
	 * Affiche un message 'message' aux coordonnées 'coord' pendant le temps
	 * 'duree'
	 *
	 * @param coord les coordonnées du message
	 * @param message le message à afficher
	 * @param duree le temps d'affichage du message
	 */
	public void afficherMessage(Point coord, String message, int duree) {
		Graphics2D g2 = ImageOutils.viderBufferedImage(bufferMessage);
		g2.setFont(new Font("impact", Font.BOLD, 20));
		FontMetrics fm = g2.getFontMetrics();
		int taille = fm.stringWidth(message);
		g2.drawString(message, (int) coord.x - taille / 2, coord.y);
		camera.setFreeze(true);

		Timer timer = new Timer(0, new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				masquerMessage();
				camera.setFreeze(false);
			}
		});

		timer.setRepeats(false);
		timer.setInitialDelay(duree * 1000);
		timer.start();
		repaint();


	}

	/**
	 * Masque le message affiché avec afficherMessage
	 */
	public void masquerMessage() {
		ImageOutils.viderBufferedImage(bufferMessage);
		repaint();
	}

	/**
	 * Met en valeur les pions passées en paramètres pour montrer qu'ils sont
	 * attaquables
	 *
	 * @param PionsAttaquable
	 */
	public void afficherZoneAttaquable(ArrayList<Pion> PionsAttaquable) {
		Graphics2D g2 = (Graphics2D) bufferDeplacements.createGraphics();

		for (Pion p : PionsAttaquable) {
			g2.setColor(Color.red);
			g2.drawOval(p.getCase().getX() * Plateau.TAILLE_CASE, p.getCase().getY() * Plateau.TAILLE_CASE, Plateau.TAILLE_CASE, Plateau.TAILLE_CASE);
		}
		actualiserBuffers();
	}

	/**
	 * Met en valeur les cases passées en paramètres pour montrer qu'elles sont
	 * potentiellement attaquables
	 *
	 * @param casesAttaquables liste des cases à la portée du pion
	 */
	public void afficherCaseAttaquable(ArrayList<Case> casesAttaquables) {
		Graphics2D g2 = (Graphics2D) bufferDeplacements.createGraphics();
		BufferedImage bi = GestionnaireImage.getInstance().get("src/donnees/images/selectionrouge.png");
		for (Case c : casesAttaquables) {
			g2.drawImage(bi, null, c.getX() * Plateau.TAILLE_CASE, c.getY() * Plateau.TAILLE_CASE);
		}
		actualiserBuffers();
	}

	/**
	 * Met en valeur les cases passées en paramètre qui représentent les cases
	 * que le pion peut atteindre. La transparence de ces cases est déterminée
	 * selon la taille du trajet à effectuer pour s'y rendre.
	 *
	 * @param casesDeplacement liste des cases accessibles par le pion ainsi que
	 * la distance à parcourir pour s'y rendre
	 */
	public void afficherZoneDeplacement(Set<Map.Entry<Case, Double>> casesDeplacement) {
		bufferDeplacements = new BufferedImage(plateau.getLargeur() * Plateau.TAILLE_CASE, plateau.getHauteur() * Plateau.TAILLE_CASE, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2 = (Graphics2D) bufferDeplacements.createGraphics();
		double distanceMax = 0;
		for (Map.Entry<Case, Double> caseDistance : casesDeplacement) {
			Double distance = caseDistance.getValue();
			if (distance > distanceMax) {
				distanceMax = distance;
			}
		}

		for (Map.Entry<Case, Double> caseDistance : casesDeplacement) {
			Case c = caseDistance.getKey();
			Double distance = caseDistance.getValue();
			float transparence = (float) (0.8 - distance / distanceMax * 0.6);
			BufferedImage bi = ImageOutils.copyAlpha(GestionnaireImage.getInstance().get("src/donnees/images/selectionbleue.png"), transparence);
			g2.drawImage(bi, null, c.getX() * Plateau.TAILLE_CASE, c.getY() * Plateau.TAILLE_CASE);
		}
		actualiserBuffers();
	}

	/**
	 * Réinitialise la BufferedImage contenant les cases sur lesquelles un pion
	 * peut se rendre
	 */
	public void effacerZoneDeplacement() {
		bufferDeplacements = new BufferedImage(plateau.getLargeur() * Plateau.TAILLE_CASE, plateau.getHauteur() * Plateau.TAILLE_CASE, BufferedImage.TYPE_INT_ARGB);
		actualiserBuffers();
	}

	/**
	 * Affiche l'image 'img' dans la case 'c'
	 *
	 * @param img l'image à afficher
	 * @param c la case où l'on doit afficher l'image
	 */
	public void afficherImage(BufferedImage img, Case c) {
		Graphics2D g2 = (Graphics2D) bufferPions.createGraphics();
		Rectangle vue = camera.getRectangle();
		int x = c.getX();
		int y = c.getY();
		g2.drawImage(img, null, x * Plateau.TAILLE_CASE - vue.x, y * Plateau.TAILLE_CASE - vue.y);
		repaint();
	}

	/**
	 *
	 * @return le buffer servant aux animations
	 */
	public BufferedImage getBufferAnimation() {
		return bufferAnimation;
	}

	/**
	 * Affiche une image tournée de 'angle'
	 *
	 * @param img l'image à afficher
	 * @param c la case où l'on doit afficher l'image
	 * @param angle l'angle de rotation
	 */
	public void afficherImage(BufferedImage img, Case c, double angle) {
		Graphics2D g2 = (Graphics2D) bufferPions.createGraphics();
		Rectangle vue = camera.getRectangle();
		int x = c.getX();
		int y = c.getY();
		AffineTransform at = new AffineTransform();
		at.translate(x * Plateau.TAILLE_CASE - vue.x + img.getWidth() / 2, y * Plateau.TAILLE_CASE - vue.y + img.getHeight() / 2);
		at.rotate(angle);
		at.translate(-img.getWidth() / 2, -img.getHeight() / 2);
		g2.drawImage(img, at, null);
	}

	/**
	 * Affiche l'animation d'explosion de la croix
	 *
	 * @param bCroix
	 * @param contenuBoutton
	 */
	public void explosion(JButton bCroix, Icon contenuBoutton) {
		Pion pionSelect = ((EcouteurDeSourisJeu) ecouteur).getPionSelectionne();
		if (pionSelect != null) {
			Point coordPlateau = pionSelect.getCase().getCoord();
			Rectangle vue = camera.getRectangle();
			int xHG = (coordPlateau.x) * Plateau.TAILLE_CASE - vue.x;
			int yHG = (coordPlateau.y) * Plateau.TAILLE_CASE - vue.y;
			BufferedImage croix = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
			Graphics2D g2 = (Graphics2D) croix.createGraphics();
			Graphics2D dep = (Graphics2D) bufferPions.createGraphics();

			g2.drawImage(GestionnaireImage.getInstance().get("src/donnees/images/Croix.png"), null, 0, 0);

			BufferedImage tabCarres[] = new BufferedImage[25];
			Point coordCarres[] = new Point[25];
			for (int x = 0; x < 5; x++) {
				for (int y = 0; y < 5; y++) {
					tabCarres[y * 5 + x] = croix.getSubimage(x * 10, y * 10, 10, 10);
					coordCarres[y * 5 + x] = new Point(x * 10 + xHG, y * 10 + yHG);
				}
			}

			for (int x = 0; x < 5; x++) {
				for (int y = 0; y < 5; y++) {
					coordCarres[y * 5 + x].x += (x - 2) * 5;
					coordCarres[y * 5 + x].y += (y - 2) * 5;
				}
			}

			int delaisInterFrames = 50;
			int tempsAnimation = 1000;
			final int nbFrames = tempsAnimation / delaisInterFrames;

			final Timer timer = new Timer(delaisInterFrames, null);
			timer.addActionListener(new ActionListener() {
				int nbFramesRestantes = nbFrames;
				EcouteurDeSourisJeu ecouteurTmp = (EcouteurDeSourisJeu) ecouteur;

				@Override
				public void actionPerformed(ActionEvent e) {
					if (nbFramesRestantes == 0) {
						timer.stop();
						camera.setFreeze(false);
						setEcouteur(ecouteurTmp);
					} else {
					}

				}
			});

			timer.start();
			setEcouteur(null);
			repaint();

			for (int i = 0; i < 25; i++) {
				dep.drawImage(tabCarres[i], null, coordCarres[i].x, coordCarres[i].y);
			}
			repaint();


			bCroix.setIcon(contenuBoutton);
		}
	}

	/**
	 * Dessine le coup special 'coupSpecial' sur l'aire de jeu
	 *
	 * @param coupSpecial
	 */
	public void dessinerCoupSpecial(CoupSpecial coupSpecial) {
		Graphics2D g2 = (Graphics2D) bufferDeplacements.createGraphics();
		coupSpecial.dessiner(g2, camera);
		if (coupSpecial.isAfficherCasesAffectees()) {
			afficherCaseAttaquable(coupSpecial.porteeCoupSpecial(plateau));
		}
		repaint();
	}

	/**
	 * Masque les indications de déplacementss
	 */
	public void masquerDeplacement() {
		ImageOutils.viderBufferedImage(bufferDeplacements);
		repaint();
	}
}
